<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>文件分片上传 Demo</title>
</head>

<body>
    <h2>文件分片上传（断点续传 + 并发 + 压缩 + 统计）</h2>
    <input type="file" id="fileInput" />
    <label>
        <input type="checkbox" id="compressCheckbox" />
        启用 Gzip 压缩
    </label>
    <button onclick="upload()">上传</button>
    <p id="status"></p>
    <p id="time"></p>

    <script>
        async function upload() {
            const file = document.getElementById("fileInput").files[0];
            const useCompress = document.getElementById("compressCheckbox").checked;
            if (!file) return;

            const chunkSize = 1024 * 1024; // 每片 1MB
            const totalChunks = Math.ceil(file.size / chunkSize);
            const fileName = file.name;

            const startTime = performance.now(); // ✅ 记录开始时间

            document.getElementById("status").innerText = `检查已上传分片...`;
            const uploadedResp = await fetch(`http://localhost:8080/uploaded?filename=${fileName}`);
            const uploadedChunks = await uploadedResp.json();

            document.getElementById("status").innerText = `准备上传，已存在 ${uploadedChunks.length} 分片`;

            const tasks = [];
            for (let i = 0; i < totalChunks; i++) {
                if (uploadedChunks.includes(i.toString())) {
                    console.log(`跳过分片 ${i}`);
                    continue;
                }

                tasks.push(() => uploadChunk(file, fileName, i, chunkSize, totalChunks, useCompress));
            }

            await limitConcurrentDynamic(tasks, 4);

            document.getElementById("status").innerText = "所有分片上传完成，开始合并...";
            await fetch(`http://localhost:8080/merge?filename=${fileName}&total=${totalChunks}`);

            const endTime = performance.now(); // ✅ 记录结束时间
            const duration = ((endTime - startTime) / 1000).toFixed(2);
            document.getElementById("status").innerText = "上传并合并完成 🎉";
            document.getElementById("time").innerText = `总耗时：${duration} 秒`;
        }

        async function uploadChunk(file, fileName, index, chunkSize, total, useCompress) {
            const chunk = file.slice(index * chunkSize, (index + 1) * chunkSize);

            let blobToSend = chunk;
            let isCompressed = false;

            if (useCompress) {
                const buffer = await chunk.arrayBuffer();
                const compressedBuffer = await compressGzip(buffer);
                blobToSend = new Blob([compressedBuffer]);
                isCompressed = true;
            }

            const formData = new FormData();
            formData.append("file", blobToSend);
            formData.append("index", index);
            formData.append("filename", fileName);
            if (isCompressed) {
                formData.append("compressed", "1");
            }

            await fetch("http://localhost:8080/upload", {
                method: "POST",
                body: formData
            });

            document.getElementById("status").innerText = `上传中 ${index + 1}/${total}`;
        }

        async function limitConcurrentDynamic(tasks, initialLimit = 4) {
            let concurrency = initialLimit;
            const results = [];
            const executing = new Set();
            const speedSamples = [];
            const maxConcurrency = 10;
            const minConcurrency = 1;
            const speedThreshold = 100 * 1024; // 100KB/s

            // 启动新任务
            const runTask = async (task) => {
                const start = performance.now();
                await task();
                const end = performance.now();
                const duration = (end - start) / 1000;
                const size = 1024 * 1024; // 1MB
                const speed = size / duration;
                speedSamples.push(speed);

                if (speedSamples.length > 5) speedSamples.shift();

                const avgSpeed = speedSamples.reduce((a, b) => a + b, 0) / speedSamples.length;

                if (avgSpeed > speedThreshold && concurrency < maxConcurrency) {
                    concurrency++;
                } else if (avgSpeed < speedThreshold && concurrency > minConcurrency) {
                    concurrency = Math.max(minConcurrency, Math.floor(concurrency / 2));
                }
            };

            while (tasks.length > 0 || executing.size > 0) {
                while (executing.size < concurrency && tasks.length > 0) {
                    const task = tasks.shift();
                    const p = runTask(task).then(() => executing.delete(p));
                    executing.add(p);
                    results.push(p);
                }
                // 等待任一任务完成，空出位置
                if (executing.size > 0) {
                    await Promise.race(executing);
                }
            }

            await Promise.all(results);
        }


        async function compressGzip(buffer) {
            const cs = new CompressionStream("gzip");
            const writer = cs.writable.getWriter();
            writer.write(new Uint8Array(buffer));
            writer.close();

            const compressed = await new Response(cs.readable).arrayBuffer();
            return compressed;
        }
    </script>
</body>

</html>